using System;
using System.Collections;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;


namespace Autobot
{
    class CommunicatorServer : Communicator
    {
        private String ip_address_str = null;
        private int port = 0;
        private ArrayList client_list;
        private Socket socket_server;

        /**************************************************************
         * PUBLIC CLASS FUNCTIONS (API)
         **************************************************************/

        /**
        * Name: void Initialize()
        * Description: Initializes the communicator.
        */
        public override bool Initialize(LogController a_log, string a_ip)
        {
            //a_ip is ignored
            port = 12345;
            log = a_log;

            socket_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            client_list = new ArrayList();

            // Get my ip addresses, find the correct one, store it as a string
            IPHostEntry ihe = Dns.GetHostEntry(Dns.GetHostName());
            ip_address_str = FindMyIPString(ihe.AddressList, ihe.AddressList.Length);

            // Try binding the socket to my ip address
            try
            {
                IPAddress ip_address = IPAddress.Parse(ip_address_str);
                IPEndPoint ip_endpoint = new IPEndPoint(ip_address, port);
                socket_server.Bind(ip_endpoint);
                socket_server.Listen(10);
            }
            catch
            {
                this.GetLog().WriteLog("COMMUNICATOR SERVER failed to initialize connection!  IP="+ip_address_str);
                return false;
            }

            this.GetLog().WriteLog("COMMUNICATOR SERVER connected!  IP=" + ip_address_str);
            this.StartMessageLoop();
            return true;
        }

        /**
         * Name: void Close()
         * Description: Closes the communicator.
         */
        public override void Close()
        {
            int i;
            Client c;

            // Clear the client list
            for (i = 0; i < client_list.Count; i++)
            {
                c = (Client)client_list[i];
                client_list.RemoveAt(i);
                c.GetSocket().Close();
            }

            socket_server.Close();
            this.GetLog().WriteLog("COMMUNICATOR SERVER closed. IP:" + ip_address_str + " Port:" + port);
            thread.Abort();
        }

        /**
         * Name: string SendMessage(UInt32 client_id, Message a_message)
         * Description: Sends a message to the client with id 'client_id'.  It is converted into a string before being passed to the Send() function
         * Input: The 'Message' to send.
         * Output: void
         */
        public override void SendMessage(UInt32 client_id, Message message)
        {
            Client client = get_client_by_id(client_id);
            if (client_id != 0)
            {
                StreamWriter streamWriter = client.GetStreamWriter(); 
                Send(streamWriter, message.SaveToString());
            }
        }

        public Socket GetClientSocket(UInt32 a_id)
        {
            Client c = get_client_by_id(a_id);
            if (c == null)
            {
                return null;
            }
            else
            {
                return c.GetSocket();
            }
        }

        public string GetClientName(UInt32 a_id)
        {
            Client c = get_client_by_id(a_id);
            if (c == null)
            {
                return null;
            }
            else
            {
                return c.GetName();
            }
        }

        public ClientType GetClientType(UInt32 a_id)
        {
            Client c = get_client_by_id(a_id);
            if (c == null)
            {
                return ClientType.DEFAULT;
            }
            else
            {
                return c.GetClientType();
            }
        }

        public void SetClientName(UInt32 a_id, string a_name)
        {
            Client c = get_client_by_id(a_id);
            if (c != null)
            {
                c.SetName(a_name);
            }
            return;
        }

        public void SetClientType(UInt32 a_id, ClientType a_type)
        {
            Client c = get_client_by_id(a_id);
            if (c != null)
            {
                c.SetClientType(a_type);
            }
            return;
        }

        public void DisconnectClient(UInt32 a_id)
        {
            remove_client(a_id);
            return;
        }    

        /**************************************************************
         * PRIVATE CLASS FUNCTIONS
         **************************************************************/

        // This message loop will be on its own thread
        protected override void MessageLoop()
        {
            ArrayList ret_list;
            Message message;
            int i;

            if (message_queue == null)
            {
                return;
            }

            this.GetLog().WriteLog("COMMUNICATOR SERVER now in message loop");
            
            while (true)
            {
                /* Get next messages from socket */
                ret_list = this.GetMessages();

                /* Process the list of messages */
                if (ret_list != null)
                {
                    for (i = 0; i < ret_list.Count; i++)
                    {
                        RecvRet ret = (RecvRet)ret_list[i];
                        message = ParseMessage(ret.str);
                        if (message != null)
                        {
                            message.SetClientID(ret.client);

                            /* Lock the messageQueue and add the message */
                            lock (message_queue)
                            {
                                // Avoid the backlog of too many messages! Ring Buffer!
                                if (message_queue.Count > 20)
                                    message_queue.Dequeue();
                                message_queue.Enqueue(message);
                            }

                            log.WriteLog("COMMUNICATOR SERVER Message Received:" + message.SaveToString());
                        }
                    }
                    ret_list.Clear();
                }
                else
                {
                    break; //socket has died, close this thread
                }

            }

            return; //end of thread
        }



        protected ArrayList GetMessages()
        {
            int i;
            ArrayList read_list = new ArrayList();
            ArrayList message_list = new ArrayList();
            UInt32 client_id;
            Client c;
 
            /* add active sockets to listen list */
            read_list.Add(socket_server);

            for (i = 0; i < client_list.Count; i++)
            {
                c = (Client)client_list[i];
                read_list.Add(c.GetSocket());
            }
            /***/

            /* run select multiplexing */
            try
            {
                Socket.Select(read_list, null, null, 100000);
            }
            catch
            {
                //lets put this to blank
            }
            /***/

            /* parse reads */
            for (i = 0; i < read_list.Count; i++)
            {
                Socket s2 = (Socket)read_list[i];
                if (s2 == null)
                {
                    return null;
                }

                if (s2.Equals(socket_server))
                {
                    /* The server has an incoming client */
                    Socket s3 = null;
                    try
                    {
                        s3 = connect_client();
                    }
                    catch (ObjectDisposedException)
                    {
                        return null;
                    }

                    // Client is now connected
                    if (s3 != null)
                    {
                        // Create a profile for the client
                        client_id = create_client(s3);
                
                        // Socket id 0 is reserved for the server?
                        if (client_id != 0)
                        {
                            /* Add CLIENT_CONNECT message to Message Return List */
                            RecvRet msg = new RecvRet();
                            msg.client = client_id;
                            msg.str = System.String.Format("[0=6142][type={0}][client={1}][client_type={2}][ip_address={3}]",
                                (byte)MessageType.CLIENT_CONNECT,
                                client_id,
                                (byte)ClientType.DEFAULT,
                                "1.2.3.4");
                            message_list.Add(msg);
                            /***/

                            /* Server sends a 'hello' message to acknowledge the connection */
                            Message reply = new Message();
                            reply.SetMessageType(MessageType.CLIENT_CONNECT);
                            MessageField mf = new MessageField("debug1", "hello1");
                            reply.SetField(mf);
                            mf = new MessageField("debug2", "hello2");
                            reply.SetField(mf);
                            mf = new MessageField("debug3", "hello3");
                            reply.SetField(mf);
                            SendMessage(client_id, reply);
                            /***/
                        }

                    }
                    /***/
                }
                else
                {
                    // Receive a string from socket
                    Client client = get_client_by_id(identify_client(s2));
                    string r_string = null;
                    if (client != null)
                    {
                        r_string = Recv(client.GetStreamReader());
                    }
                    // If there was an IO error, we have lost contact with the client
                    if (r_string == null)
                    {
                        /* Add CLIENT_DISCONNECT message to Message Return List */
                        RecvRet msg = new RecvRet();
                        msg.client = identify_client(s2);
                        msg.str = System.String.Format("[0=6142][type={0}]", (byte)MessageType.CLIENT_DISCONNECT);
                        message_list.Add(msg);
                        /***/

                        remove_client(s2);
                        this.GetLog().WriteLog("COMMUNICATOR SERVER: Recv(): client initiated a disconnect");
                    }
                    else
                    {
                        /* Add recieved message to Message Return List */
                        RecvRet msg = new RecvRet();
                        msg.client = identify_client(s2);
                        msg.str = r_string;
                        message_list.Add(msg);
                        /***/
                    }
                }
            }
            /***/

            read_list.Clear();
            return message_list;
        }

        private UInt32 create_client(Socket a_socket)
        {
            Client c = new Client(a_socket);

            try
            {
                NetworkStream networkStream = null;
                System.IO.StreamReader streamReader = null;
                System.IO.StreamWriter streamWriter = null;

                networkStream = new NetworkStream(a_socket);
                streamReader = new System.IO.StreamReader(networkStream);
                streamWriter = new System.IO.StreamWriter(networkStream);

                c.InitializeStream(networkStream, streamReader, streamWriter);
            }
            catch (IOException e)
            {
                log.WriteLog("IOException from socket: " + e.Message);
                return 0;
            }



            client_list.Add(c);

            this.GetLog().WriteLog("COMMUNICATOR SERVER: Client "+c.GetID()+" Connected.  IP:" + a_socket.RemoteEndPoint.ToString());

            return c.GetID();
        }

        private Socket connect_client()
        {
            Socket socket = socket_server.Accept();
            if (socket.Connected)
            {
                return socket;
            }
            else
            {
                this.GetLog().WriteLog("COMMUNICATOR SERVER: Accept() error!");
                return null;
            }
        }

        private void remove_client(Socket a_socket)
        {
            int i;
            Client c;
            for (i = 0; i < client_list.Count; i++)
            {
                c = (Client)client_list[i];
                if (c.GetSocket() == a_socket)
                {
                    client_list.RemoveAt(i);
                    a_socket.Close();
                    this.GetLog().WriteLog("COMMUNICATOR SERVER: Client " + c.GetID() + " Terminated");

                    return;
                }
            }
        }

        private void remove_client(UInt32 a_id)
        {
            int i;
            Client c;
            for (i = 0; i < client_list.Count; i++)
            {
                c = (Client)client_list[i];
                if (c.GetID() == a_id)
                {
                    client_list.RemoveAt(i);
                    c.GetSocket().Close();
                    this.GetLog().WriteLog("COMMUNICATOR SERVER: Client " + c.GetID() + " Terminated");
                    return;
                }
            }
        }

        private UInt32 identify_client(Socket a_socket)
        {
            int i;
            Client c;
            for (i = 0; i < client_list.Count; i++)
            {
                c = (Client)client_list[i];
                if (c.GetSocket() == a_socket)
                {
                    return c.GetID();
                }
            }
            return 0;
        }

        private Client get_client_by_id(UInt32 a_id)
        {
            int i;
            Client c;
            for (i = 0; i < client_list.Count; i++)
            {
                c = (Client)client_list[i];
                if (c.GetID() == a_id)
                {
                    return c;
                }
            }
            return null;
        }

        public class RecvRet
        {
            public string str;
            public UInt32 client;
        }

    }




}